/*-----------------------------------------------------------------------------
 * @file   list.c
 * @brief  A double (forward and backward) linked list using pointers.
 * @author WLH<wanglehui8357@163.com> Copyright (C) 2021~2099.
 ----------------------------------------------------------------------------*/
#include "list.h"
#include "hal_irq.h"

void list_init(list_t* list, size_t max_nr_of_items)
{
    list->first_item = NULL;
    list->last_item = NULL;
    list->nr_of_items = 0;
    list->max_nr_of_items = max_nr_of_items;
}

void list_item_init(list_item_t* item)
{
    item->prev_item = NULL;
    item->next_item = NULL;
}

bool list_is_empty(list_t* list)
{
    return (list->first_item == NULL);
}

bool list_is_full(list_t* list)
{
    if (list->max_nr_of_items == 0)
    {
        return true;
    }
    return (list->nr_of_items >= list->max_nr_of_items);
}

size_t list_nr_of_items(list_t* list)
{
    return (list->nr_of_items);
}

list_item_t* list_first_item(list_t* list)
{
    return list->first_item;
}

list_item_t* list_last_item(list_t* list)
{
    return list->last_item;
}

list_item_t* list_next_item(list_item_t* item)
{
    return item->next_item;
}

list_item_t* list_prev_item(list_item_t* item)
{
    return item->prev_item;
}

bool list_add_to_start(list_t* list, list_item_t* item)
{
    if (list_is_full(list))
    {
        return false;
    }

    CRITICAL_SECTION_ENTER
    if (list_is_empty(list))
    {
        // Add first item
        list->first_item = item;
        list->last_item = item;
        item->next_item = NULL;
        item->prev_item = NULL;
    }
    else
    {
        // Insert new item before first item
        item->prev_item = NULL;
        item->next_item = list->first_item;
        list->first_item->prev_item = item;
        list->first_item = item;
    }

    // Increment item count
    (list->nr_of_items)++;
    CRITICAL_SECTION_LEAVE

    return true;
}

bool list_add_before(list_t* list, list_item_t* ref_item, list_item_t* item)
{
    if (list_is_empty(list) || ref_item == list->first_item)
    {
        return list_add_to_start(list, item);
    }

    CRITICAL_SECTION_ENTER
    item->next_item = ref_item;
    item->prev_item = ref_item->prev_item;

    ref_item->prev_item->next_item = item;
    ref_item->prev_item = item;

    // Increment item count
    (list->nr_of_items)++;
    CRITICAL_SECTION_LEAVE

    return true;
}

bool list_add_to_end(list_t* list, list_item_t* item)
{
    if (list_is_full(list))
    {
        return false;
    }

    CRITICAL_SECTION_ENTER
    if (list_is_empty(list))
    {
        // Add first item
        list->first_item = item;
        list->last_item = item;
        item->next_item = NULL;
        item->prev_item = NULL;
    }
    else
    {
        // Append new item to last item
        item->prev_item = list->last_item;
        item->next_item = NULL;
        list->last_item->next_item = item;
        list->last_item = item;
    }

    // Increment item count
    (list->nr_of_items)++;
    CRITICAL_SECTION_LEAVE

    return true;
}

bool list_add_after(list_t* list, list_item_t* ref_item, list_item_t* item)
{
    if (list_is_empty(list) || ref_item == list->last_item)
    {
        return list_add_to_end(list, item);
    }

    CRITICAL_SECTION_ENTER
    item->next_item = ref_item->next_item;
    item->prev_item = ref_item;

    ref_item->next_item->prev_item = item;
    ref_item->next_item = item;

    // Increment item count
    (list->nr_of_items)++;
    CRITICAL_SECTION_LEAVE

    return true;
}

list_item_t* list_remove_first_item(list_t* list)
{
    list_item_t* item = list->first_item;

    // See if list is empty
    if (list_is_empty(list))
    {
        return NULL;
    }

    CRITICAL_SECTION_ENTER
    // See if there is only one item
    if (list->first_item == list->last_item)
    {
        list->first_item = NULL;
        list->last_item = NULL;
    }
    else
    {
        // The next item become the first one in the list
        list->first_item = item->next_item;
        item->next_item->prev_item = NULL;
    }

    // Clear links of removed item
    item->prev_item = NULL;
    item->next_item = NULL;

    // Decrement item count
    (list->nr_of_items)--;
    CRITICAL_SECTION_LEAVE

    return item;
}

list_item_t* list_remove_last_item(list_t* list)
{
    list_item_t* item = list->last_item;

    // See if list is empty
    if (list_is_empty(list))
    {
        return NULL;
    }

    CRITICAL_SECTION_ENTER
    // See if there is only one item
    if (list->first_item == list->last_item)
    {
        list->first_item = NULL;
        list->last_item = NULL;
    }
    else
    {
        // The previous item become the last one in the list
        list->last_item = item->prev_item;
        item->prev_item->next_item = NULL;
    }

    // Clear links of removed item
    item->prev_item = NULL;
    item->next_item = NULL;

    // Decrement item count
    (list->nr_of_items)--;
    CRITICAL_SECTION_LEAVE

    return item;
}

void list_remove_item(list_t* list, list_item_t* item)
{
    // Extra sanity check
    if (list_is_empty(list))
    {
        return;
    }

    // See if this is the first item in the list
    if (item == list->first_item)
    {
        list_remove_first_item(list);
        return;
    }

    // See if this is the last item in the list
    if (item == list->last_item)
    {
        list_remove_last_item(list);
        return;
    }

    CRITICAL_SECTION_ENTER
    // Link previous and next item to each other
    item->prev_item->next_item = item->next_item;
    item->next_item->prev_item = item->prev_item;

    // Clear links of item
    item->prev_item = NULL;
    item->next_item = NULL;

    // Decrement item count
    (list->nr_of_items)--;
    CRITICAL_SECTION_LEAVE
}

bool list_item_in_list(list_t* list, list_item_t* item)
{
    // Start at first item in the list
    list_item_t* item_in_list = list->first_item;

    // Search all items in the list
    while (item_in_list != NULL)
    {
        if (item_in_list == item)
        {
            // Item is in the list
            return true;
        }
        // Next item
        item_in_list = item_in_list->next_item;
    }
    // Item is not in the list
    return false;
}
